/*
 * Copyright (C) 2004 by Ole Reinhardt <ole.reinhardt@kernelconcepts.de>,
 *                       Kernelconcepts http://www.kernelconcepts.de
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. Neither the name of the copyright holders nor the names of
 *    contributors may be used to endorse or promote products derived
 *    from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY EGNITE SOFTWARE GMBH AND CONTRIBUTORS
 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
 * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL EGNITE
 * SOFTWARE GMBH OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
 * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
 * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
 * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
 * THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 *
 * For additional information see http://www.ethernut.de/
 *
 */

/*!
 * \file dev/adc.c
 * \brief AVR adc driver
 */


/*
 * $Log: adc.c,v $
 * Revision 1.5  2005/10/24 17:59:55  haraldkipp
 * Fixes for ATmega103
 *
 * Revision 1.4  2005/08/25 16:23:42  olereinhardt
 * Removed compute intensive % and replaced by an &
 *
 * Revision 1.3  2005/08/19 21:52:43  freckle
 * use 8-bit buffer pointers, removed critical section from ADCRead
 *
 * Revision 1.2  2005/08/02 17:46:45  haraldkipp
 * Major API documentation update.
 *
 * Revision 1.1  2005/07/26 18:02:27  haraldkipp
 * Moved from dev.
 *
 * Revision 1.3  2005/06/13 12:00:09  olereinhardt
 * Removed unnecessary NutExitCritical
 *
 * Revision 1.2  2005/03/04 11:42:05  olereinhardt
 * Added function void ADCStartLowNoiseConversion(void)
 * This function enters sleep mode!!! Be shure to read the AVR datasheet before using this function
 *
 * Revision 1.1  2004/08/02 10:05:25  olereinhardt
 * First checkin. ADC driver for ATMega128 / GCC
 *
 */


/*!
 * \addtogroup xgAvrAdc
 */

/*@{*/

/* Not ported. */

#include <string.h>
#include "adc.h"

#if HWVER == 4
#include "programmer.h"
extern struct _typGlobalData	mGlobalData;
static struct _typAdcOverSampleSeq	typOverSampleSeq[] =
{
	{ADC0	,0	,16	,2	,4 ,0	, &mGlobalData.AdInputs[0].Raw},
	{ADC1	,0	,16	,2	,4 ,0	, &mGlobalData.AdInputs[1].Raw},
	{ADC2	,0	,16	,2	,4 ,0	, &mGlobalData.AdInputs[2].Raw},
	{ADC3	,0	,16	,2	,4 ,0	, &mGlobalData.AdInputs[3].Raw},
	{ADC4	,0	,16	,2	,4 ,0	, &mGlobalData.AdInputs[4].Raw},
	{ADC5	,0	,16	,2	,4 ,0	, &mGlobalData.AdInputs[5].Raw},
	{ADC6	,0	,16	,2	,4 ,0	, &mGlobalData.AdInputs[6].Raw},
	{ADC7	,0	,16	,2	,4 ,0	, &mGlobalData.AdInputs[7].Raw},
};
#define MAX_ADC_CHANS	8
#else
#include "main.h"
extern struct _typGlobalData	mGlobalData;
#if HWVER == 31
static struct _typAdcOverSampleSeq	typOverSampleSeq[] =
{
	{ADC0	,0	,16	,2	,4 ,0	, &mGlobalData.AdInputs[0].Raw},
	{ADC1	,0	,16	,2	,4 ,0	, &mGlobalData.AdInputs[1].Raw},
	{ADC2	,0	,16	,2	,4 ,0	, &mGlobalData.AdInputs[2].Raw},
	{ADC3	,0	,16	,2	,4 ,0	, &mGlobalData.SwAdcInputs.value[0]},
	{ADC4	,0	,16	,2	,4 ,0	, &mGlobalData.SwAdcInputs.value[1]},
	{ADC5	,0	,16	,2	,4 ,0	, &mGlobalData.SwAdcInputs.value[2]},
	{ADC6	,0	,16	,2	,4 ,0	, &mGlobalData.SwAdcInputs.value[3]},
	{ADC7	,0	,16	,2	,4 ,0	, &mGlobalData.SwAdcInputs.value[4]}
};
#define MAX_ADC_CHANS	8
#else
static struct _typAdcOverSampleSeq	typOverSampleSeq[] =
{
	{ADC5	,0	,16	,2	,4 ,0	, &mGlobalData.AdInputs[0].Raw},
	{ADC6	,0	,16	,2	,4 ,0	, &mGlobalData.AdInputs[1].Raw},
	{ADC7	,0	,16	,2	,4 ,0	, &mGlobalData.AdInputs[2].Raw}
};
#define MAX_ADC_CHANS	3
#endif

#endif

uint8_t	nSampleSeq = 0;

#ifndef ADC_INITIAL_CHANNEL
#define ADC_INITIAL_CHANNEL ADC5
#endif

#ifndef ADC_INITIAL_REF
//#define ADC_INITIAL_REF AVCC
#endif

#ifndef ADC_INITIAL_MODE
#define ADC_INITIAL_MODE SINGLE_CONVERSION
#endif

#ifndef ADC_INITIAL_PRESCALE
#define ADC_INITIAL_PRESCALE ADC_PRESCALE_DIV64
#endif

#define ADC_BUF_SIZE 3 // this may only be a power of two

#if defined(__GNUC__) && defined(__AVR_ENHANCED__)
//uint8_t adc_sleep_mode = SLEEP_MODE_ADC;

/* AT90CAN128 uses a different register to enter sleep mode */
#if defined(SMCR)
#define AVR_SLEEP_CTRL_REG    SMCR
#else
#define AVR_SLEEP_CTRL_REG    MCUCR
#endif
#endif

/********** DRIVER GLOBALS **********/
adc_mode_t current_mode;

static adc_channel_t ADC_current = ADC_INITIAL_CHANNEL;

void adc_start_sequence(void)
{
	ADCSetMode(SINGLE_CONVERSION);

	nSampleSeq = 0;

	/* Set the first channel */
	ADCSetChannel(typOverSampleSeq[0].channel);

	ADCStartConversion();
}

void getNextADC(void)
{
	// Do Previous lookup
#if HWVER != 4
	ADlookupNum(ADC_current-ADC_INITIAL_CHANNEL);
#endif
	
	if (ADC_current >= ADC7)
		ADCSetChannel(ADC_INITIAL_CHANNEL);
	else
		ADCSetChannel(ADC_current + 1);
	
	ADCStartConversion();
}

ISR(ADC_vect)	// ADC Interrupt!
{
	register unsigned char uchSeq = nSampleSeq;
	register unsigned char uchTime = TICKS_CNT;

	typOverSampleSeq[uchSeq].nAccumulator += ADCW;
	typOverSampleSeq[uchSeq].nCnt ++;

	sei();

	if ( typOverSampleSeq[uchSeq].nCnt >= typOverSampleSeq[uchSeq].nSamples )
	{
		/* Bitshift and reset values */
		typOverSampleSeq[uchSeq].nAccumulator >>= typOverSampleSeq[uchSeq].nShift;
		typOverSampleSeq[uchSeq].nAccumulator <<= typOverSampleSeq[uchSeq].nUpShift;
		
		// Save value
		*typOverSampleSeq[uchSeq].pnResult = typOverSampleSeq[uchSeq].nAccumulator;
		
#if HWVER != 4
		// Save to Global Store
		if (uchSeq < SWITCH_CNT)
		{
			if (typOverSampleSeq[uchSeq].nAccumulator > SWADC_TOGGLE)
			{
				setSw( uchSeq, 0x01); 
			}
			else
			{
				setSw( uchSeq, 0x00); 
			}
		}
		else
		{
			if (typOverSampleSeq[uchSeq].nAccumulator > SWADC_TOGGLE)
			{
				setSw2( uchSeq, 0x01); 
			}
			else
			{
				setSw2( uchSeq, 0x00); 
			}
			
			// save to new location
		}
#endif

		typOverSampleSeq[uchSeq].nAccumulator = 0;
		typOverSampleSeq[uchSeq].nCnt = 0;


		if (nSampleSeq++ >= MAX_ADC_CHANS)
		{	/* start at begining */
			nSampleSeq = 0;

			/* End Sequence */
			ADCStopConversion();
		} 
		else
		{	/* Swap channels */
			ADCStopConversion();

			ADCSetChannel(typOverSampleSeq[nSampleSeq].channel);

			ADCStartConversion();
		}
	}
	else
	{
		ADCStartConversion();
	}

	uchTime -= TICKS_CNT;

	SMALL_TICKS += uchTime;
}

void ADCInit(void)
{
	//ADCSRA = (1 << ADEN) | (1 << ADFR)| (1 << ADIF)  | (1 << ADIE) | (1 << ADPS2) | (1 << ADPS1) | (1 << ADPS0);
	//ADMUX = (1 << REFS0) | 0x06;
    ADCSetChannel(ADC_INITIAL_CHANNEL);
    ADCSetRef(ADC_INITIAL_REF);
    ADCSetMode(ADC_INITIAL_MODE);
    ADCSetPrescale(ADC_INITIAL_PRESCALE);

    // Enable ADC
    sbi(ADCSR, ADEN);

    // Enable ADC interrupt
    sbi(ADCSR, ADIE);
}

void ADCSetRef(adc_ref_t reference)
{
    ADCStopConversion();

//#ifdef __AVR_ENHANCED__
    switch (reference) {
    case AVCC:
        cbi(ADMUX, REFS1);
        sbi(ADMUX, REFS0);
        break;
    case AREF:
        cbi(ADMUX, REFS1);
        cbi(ADMUX, REFS0);
        break;
    case INTERNAL_256:
        sbi(ADMUX, REFS1);
        sbi(ADMUX, REFS0);
	case INTERNAL_110:
		sbi(ADMUX, REFS1);
        cbi(ADMUX, REFS0);
        break;
    }
//#endif /* __AVR_ENHANCED__ */
}

void ADCSetMode(adc_mode_t mode)
{
    ADCStopConversion();

    switch (mode) {
    case FREE_RUNNING:
#ifdef atmega128
        sbi(ADCSR, ADFR);
#endif
#ifdef atmega1280
		sbi(ADCSRA, ADATE);
		cbi(ADCSRB, ADTS0);
		cbi(ADCSRB, ADTS1);
		cbi(ADCSRB, ADTS2);
#endif
        current_mode = mode;
        break;
    case SINGLE_CONVERSION:
#ifdef atmega128
        cbi(ADCSR, ADFR);
#endif
#ifdef atmega1280
		cbi(ADCSRA, ADATE);
		sbi(ADCSRB, ADTS0);
		sbi(ADCSRB, ADTS1);
		sbi(ADCSRB, ADTS2);
#endif
        current_mode = mode;
        break;
    }
}

uint8_t ADCSetPrescale(uint8_t prescalar)
{

    ADCStopConversion();

    if (prescalar > 128) {
        prescalar = 128;
    }

    switch (prescalar) {
    case ADC_PRESCALE_DIV2:
        cbi(ADCSR, ADPS2);
        cbi(ADCSR, ADPS1);
        cbi(ADCSR, ADPS0);
        break;
    case ADC_PRESCALE_DIV4:
        cbi(ADCSR, ADPS2);
        sbi(ADCSR, ADPS1);
        cbi(ADCSR, ADPS0);
        break;
    case ADC_PRESCALE_DIV8:
        cbi(ADCSR, ADPS2);
        sbi(ADCSR, ADPS1);
        sbi(ADCSR, ADPS0);
        break;
    case ADC_PRESCALE_DIV16:
        sbi(ADCSR, ADPS2);
        cbi(ADCSR, ADPS1);
        cbi(ADCSR, ADPS0);
        break;
    case ADC_PRESCALE_DIV32:
        sbi(ADCSR, ADPS2);
        cbi(ADCSR, ADPS1);
        sbi(ADCSR, ADPS0);
        break;
    case ADC_PRESCALE_DIV64:
        sbi(ADCSR, ADPS2);
        sbi(ADCSR, ADPS1);
        cbi(ADCSR, ADPS0);
        break;
    case ADC_PRESCALE_DIV128:
        sbi(ADCSR, ADPS2);
        sbi(ADCSR, ADPS1);
        sbi(ADCSR, ADPS0);
        break;

    default:
        return 1;
        break;
    }

    return 0;
}

void ADCSetChannel(adc_channel_t adc_channel)
{
    ADMUX=(ADMUX & 0xC0) | adc_channel;
	ADC_current = adc_channel;
}

/*
void read_adc(unsigned char adc_input)
{
	uint16_t value;
	ADMUX=(ADMUX & 0xC0) | adc_input;
	// Start the AD conversion
	ADCSRA|=0x40;
	// Wait for the AD conversion to complete
	while ((ADCSRA & 0x10)==0);
	ADCSRA|=0x10;
	value = ADCW;
	setMAD((adc_input-0x05), &value);
	//return value;
}*/

void ADCStartConversion()
{
    sbi(ADCSR, ADSC);
}

void ADCStopConversion()
{
    if (current_mode == SINGLE_CONVERSION) {
        // Send warning message
        return;
    }
// Terminate and restart the ADC 
// When restarted, start_conversion needs to be
// called again
    cbi(ADCSR, ADEN);
    cbi(ADCSR, ADSC);
    sbi(ADCSR, ADEN);
}

inline adc_mode_t ADCGetMode(void)
{
    return (current_mode);
}

//INTERRUPT(SIG_ADC)
/*ISR(ADC_vect)
{
	ADCInterrupt();
}*/
